package com.transfiguro.visitors;

import com.transfiguro.StatementSet;
import com.transfiguro.datamodel.javamodels.*;
import japa.parser.ast.body.VariableDeclarator;
import japa.parser.ast.expr.Expression;
import japa.parser.ast.expr.MethodCallExpr;
import japa.parser.ast.expr.VariableDeclarationExpr;
import japa.parser.ast.stmt.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Logic is defined by a set of statements. This class provides methods to visit a given
 * {@link Statement} instance and build an instance of a {@link com.transfiguro.datamodel.Statement}
 * implementation
 */
public class StatementVisitor {
    private final Logger logger = LoggerFactory.getLogger(StatementVisitor.class);

    /**
     * Constructor
     */
    public StatementVisitor() {
        //Provided for future use
    }

    /**
     * This method visits a statement and builds an instance of a {@link com.transfiguro.datamodel.Statement}
     * instance. Every new statement added to the datamodel has to
     * be added to the <code>StatementSet</code> enum
     * in the parser to support visiting
     *
     * @param statement The <code>statement</code> to be visited
     * @return The datamodel for that <code>statement</code>
     */
    public com.transfiguro.datamodel.Statement visit(final Statement statement) {
        StatementSet set;
        logger.info("Statement: {}", statement.toString());
        logger.info("Statement type: {}", statement.getClass().getName());
        try {
            set = StatementSet.valueOf(statement.getClass().getSimpleName());
        } catch (final IllegalArgumentException e) {
            set = StatementSet.Default;
        }

        com.transfiguro.datamodel.Statement result = null;
        switch (set) {
            case IfStmt:
                IfStmt ifStmt = (IfStmt) statement;
                BlockStatement blockStatement = new BlockStatement();
                blockStatement.setBlockStart("if(" + ifStmt.getCondition().toString() + ")");
                if (ifStmt.getThenStmt() != null) {
                    blockStatement.addStatement(this.visit(ifStmt.getThenStmt()));
                }
                result = blockStatement;
                break;
            case ForStmt:
                ForStmt stmt = (ForStmt) statement;
                ForStatement forStatement = new ForStatement();
                StringBuilder loop = new StringBuilder();
                loop.append("for(");
                if (stmt.getInit() != null) {
                    loop.append(stmt.getInit().toString()).append(';');
                }
                if (stmt.getCompare() != null) {
                    loop.append(stmt.getCompare().toString());
                }
                if (stmt.getUpdate() != null) {
                    loop.append(stmt.getUpdate().toString());
                }
                loop.append(")");
                if (stmt.getInit() != null) {
                    for (final Expression exp : stmt.getInit()) {
                        forStatement.addInit(getForInit(exp));
                    }
                }
                if (stmt.getCompare() != null) {
                    forStatement.addInit(getForInit(stmt.getCompare()));
                }
                if (stmt.getUpdate() != null) {
                    for (final Expression exp : stmt.getUpdate()) {
                        forStatement.addInit(getForInit(exp));
                    }
                }
                forStatement.setLoop(loop.toString());
                if (stmt.getBody() != null) {
                    forStatement.addStatement(this.visit(stmt.getBody()));
                }
                logger.info("Converted For: \n{}", forStatement.toString());
                result = forStatement;
                break;
            case ForeachStmt:
                ForeachStmt forEachStmt = (ForeachStmt) statement;
                ForEachStatement forEachStatement = new ForEachStatement();

                Statement internalStatement = forEachStmt.getBody();
                if (internalStatement != null) {
                    forEachStatement.addStatement(this.visit(internalStatement));
                }

                forEachStatement.setLoopBody(forEachStmt.getBody().toString());
                forEachStatement.setIterator(forEachStmt.getIterable());
                forEachStatement.setVariable(forEachStmt.getVariable());
                result = forEachStatement;
                logger.info("Found a for each statement: \n {}", forEachStatement.toString());
                break;
            case ExpressionStmt:
                Expression expression = ((ExpressionStmt) statement).getExpression();
                StatementSet subType;
                try {
                    subType = StatementSet.valueOf(expression.getClass().getSimpleName());
                } catch (final IllegalArgumentException e) {
                    subType = StatementSet.Default;
                }
                logger.info("Subtype: {}", subType.toString());
                switch (subType) {
                    case VariableDeclarationExpr:
                        logger.info("Found a variable declaration expression");
                        com.transfiguro.datamodel.Statement innerResult = null;
                        VariableDeclarationExpr expr = (VariableDeclarationExpr) expression;
                        VariableDeclarationStatement varStmt = new VariableDeclarationStatement();
                        varStmt.setType(expr.getType().toString().trim());
                        varStmt.setAccessModifier(expr.getModifiers());
                        VariableDeclarator declarator = expr.getVars().get(0);
                        varStmt.setVariableName(declarator.getId().getName());
                        if (declarator.getInit() != null) {
                            varStmt.setHasValue(true);
                            //Copying the expression directly. Converter will take care of making it a string
                            StatementSet subSubType;
                            Expression innerExpression = declarator.getInit();
                            try {
                                subSubType = StatementSet.valueOf(declarator.getInit().getClass()
                                        .getSimpleName());
                            } catch (final IllegalArgumentException e) {
                                subSubType = StatementSet.Default;
                            }
                            switch (subSubType) {
                                case MethodCallExpr:
                                    logger.info("Found a method call statement");
                                    MethodCallExpr methodCallExpr = (MethodCallExpr) innerExpression;
                                    logger.info("{}", innerExpression.toString());
                                    MethodCallStatement methodCallStatement = new MethodCallStatement();
                                    if (methodCallExpr.getArgs() != null) {
                                        methodCallStatement.setArgs(methodCallExpr.getArgs().toString()
                                                .replace("[", "").replace("]", "").trim());
                                    }
                                    //TODO Now adding parent library to method name. Find a better way of doing this
                                    if (methodCallExpr.getScope() != null) {
                                        logger.info("Scope: {}", methodCallExpr.getScope().toString());
                                        methodCallStatement.setScope(methodCallExpr.getScope().toString());
                                    }
                                    methodCallStatement.setMethodName(methodCallExpr.getName());
                                    innerResult = methodCallStatement;
                                    break;
                                case Default:
                                    NCBlock unDefinedStatement = new NCBlock();
                                    unDefinedStatement.setStatement(innerExpression.toString());
                                    innerResult = unDefinedStatement;
                                    logger.info("Expression Type Not Supported: {}", statement.toString());
                                    break;
                            }
                            varStmt.addStatement(innerResult);
                            varStmt.setValue(declarator.getInit());
                        }
                        result = varStmt;
                        break;
                    case MethodCallExpr:
                        logger.info("Found a method call statement");
                        MethodCallExpr methodCallExpr = (MethodCallExpr) expression;
                        logger.info("{}", expression.toString());
                        MethodCallStatement methodCallStatement = new MethodCallStatement();
                        if (methodCallExpr.getArgs() != null) {
                            methodCallStatement.setArgs(methodCallExpr.getArgs().toString()
                                    .replace("[", "").replace("]", "").trim());
                        }
                        //TODO Now adding parent library to method name. Find a better way of doing this
                        if (methodCallExpr.getScope() != null) {
                            logger.info("Scope: {}", methodCallExpr.getScope().toString());
                            methodCallStatement.setScope(methodCallExpr.getScope().toString());
                        }
                        methodCallStatement.setMethodName(methodCallExpr.getName());
                        if (!methodCallExpr.getArgs().isEmpty()) {
                            for (final Expression exp : methodCallExpr.getArgs()) {
                                methodCallStatement.setArgs(exp.toString());
                            }
                        }
                        result = methodCallStatement;
                        break;
                    case Default:
                        NCBlock unDefinedStatement = new NCBlock();
                        unDefinedStatement.setStatement(statement.toString());
                        result = unDefinedStatement;
                        logger.info("Expression Type Not Supported: {}", statement.toString());
                        break;
                }
                break;
            default:
                NCBlock unDefinedStatement = new NCBlock();

                if (statement instanceof BlockStmt) {
                    BlockStmt blockStmt = (BlockStmt) statement;
                    if (blockStmt.getStmts() != null) {
                        for (Statement inStatement : blockStmt.getStmts()) {
                            unDefinedStatement.addStatement(this.visit(inStatement));
                        }
                    }
                }

                unDefinedStatement.setStatement(statement.toString());
                result = unDefinedStatement;
                logger.info("Statement not defined yet: {}", statement.toString());
                break;
        }
        return result;
    }

    public com.transfiguro.datamodel.Statement getForInit(final Expression expression) {
        StatementSet subSubType;
        com.transfiguro.datamodel.Statement result = null;
        logger.info("For statement exp: {}", expression.getClass()
                .getSimpleName());
        try {
            subSubType = StatementSet.valueOf(expression.getClass()
                    .getSimpleName());
        } catch (final IllegalArgumentException e) {
            subSubType = StatementSet.Default;
        }
        switch (subSubType) {
            case VariableDeclarationExpr:
                logger.info("Found a variable declaration expression");
                com.transfiguro.datamodel.Statement innerResult = null;
                VariableDeclarationExpr expr = (VariableDeclarationExpr) expression;
                VariableDeclarationStatement varStmt = new VariableDeclarationStatement();
                varStmt.setType(expr.getType().toString().trim());
                varStmt.setAccessModifier(expr.getModifiers());
                VariableDeclarator declarator = expr.getVars().get(0);
                varStmt.setVariableName(declarator.getId().getName());
                if (declarator.getInit() != null) {
                    varStmt.setHasValue(true);
                    //Copying the expression directly. Converter will take care of making it a string
                    StatementSet ssType;
                    Expression innerExpression = declarator.getInit();
                    try {
                        ssType = StatementSet.valueOf(declarator.getInit().getClass()
                                .getSimpleName());
                    } catch (final IllegalArgumentException e) {
                        ssType = StatementSet.Default;
                    }
                    switch (ssType) {
                        case MethodCallExpr:
                            logger.info("Found a method call statement");
                            MethodCallExpr methodCallExpr = (MethodCallExpr) innerExpression;
                            logger.info("{}", innerExpression.toString());
                            MethodCallStatement methodCallStatement = new MethodCallStatement();
                            if (methodCallExpr.getArgs() != null) {
                                methodCallStatement.setArgs(methodCallExpr.getArgs().toString()
                                        .replace("[", "").replace("]", "").trim());
                            }
                            //TODO Now adding parent library to method name. Find a better way of doing this
                            if (methodCallExpr.getScope() != null) {
                                logger.info("Scope: {}", methodCallExpr.getScope().toString());
                                methodCallStatement.setScope(methodCallExpr.getScope().toString());
                            }
                            methodCallStatement.setMethodName(methodCallExpr.getName());
                            if (!methodCallExpr.getArgs().isEmpty()) {
                                for (final Expression exp : methodCallExpr.getArgs()) {
                                    methodCallStatement.setArgs(exp.toString());
                                }
                            }
                            innerResult = methodCallStatement;
                            break;
                        case Default:
                            NCBlock unDefinedStatement = new NCBlock();
                            unDefinedStatement.setStatement(innerExpression.toString());
                            innerResult = unDefinedStatement;
                            break;
                    }
                    varStmt.addStatement(innerResult);
                    varStmt.setValue(declarator.getInit());
                }
                result = varStmt;
                break;
            case MethodCallExpr:
                logger.info("Found a method call statement");
                MethodCallExpr methodCallExpr = (MethodCallExpr) expression;
                logger.info("{}", expression.toString());
                MethodCallStatement methodCallStatement = new MethodCallStatement();
                if (methodCallExpr.getArgs() != null) {
                    methodCallStatement.setArgs(methodCallExpr.getArgs().toString()
                            .replace("[", "").replace("]", "").trim());
                }
                //TODO Now adding parent library to method name. Find a better way of doing this
                if (methodCallExpr.getScope() != null) {
                    logger.info("Scope: {}", methodCallExpr.getScope().toString());
                    methodCallStatement.setScope(methodCallExpr.getScope().toString());
                }
                methodCallStatement.setMethodName(methodCallExpr.getName());
                result = methodCallStatement;
                break;
            case Default:
                NCBlock unDefinedStatement = new NCBlock();
                unDefinedStatement.setStatement(expression.toString());
                result = unDefinedStatement;
                break;
        }
        return result;
    }
}